package com.shigi.knapsack.solvers.ga;

import com.shigi.knapsack.KnapsackItem;

import java.util.Random;

/**
 * Created by Miroslav Šiagi on 01/12/14.
 */
public class GAUtils {

    public static double computeFitnessValue(KnapsackIndividual knapsackIndividual, KnapsackPopulation knapsackPopulation) {
        double fitnessValue = -1.0;
        int capacity = knapsackPopulation.getCapacity();

        int weightSumOfUsedItems = getWeightOfUsedItems(knapsackIndividual);

        if (weightSumOfUsedItems <= capacity) {
            fitnessValue = getValueSumOfUsedItems(knapsackIndividual);
        } else {
            fitnessValue = capacity - weightSumOfUsedItems;
        }

        return fitnessValue;
    }

    private static void changeItemsRandomly(KnapsackIndividual knapsackIndividual) {
        KnapsackItem[] knapsackItems = knapsackIndividual.getKnapsackItems();
        for(KnapsackItem knapsackItem : knapsackItems) {
            if(knapsackItem.isUsed() && Math.random() < 0.005) {
                knapsackItem.setUsed(false);
            }
        }
    }

    public static double computeFitnessOnPopulation(KnapsackPopulation knapsackPopulation) {
        double bestFitness = Double.NEGATIVE_INFINITY;

        KnapsackIndividual[] knapsackIndividuals = knapsackPopulation.getKnapsackIndividuals();
        for(KnapsackIndividual knapsackIndividual : knapsackIndividuals) {
            double individualFitness = computeFitnessValue(knapsackIndividual, knapsackPopulation);
            knapsackIndividual.setFitnessValue(individualFitness);

            if(individualFitness >= bestFitness) {
                bestFitness = individualFitness;
            }
        }

        return bestFitness;
    }

    public static KnapsackIndividual performReproduction(KnapsackPopulation knapsackPopulation, GAConfig gaConfig) {
        return performLinearRankSelection(knapsackPopulation, gaConfig);
    }

    public static KnapsackIndividual performTournametnSelection(KnapsackPopulation knapsackPopulation, GAConfig gaConfig) {
        Random random = new Random(System.currentTimeMillis());
        int firstIndex = random.nextInt(knapsackPopulation.getKnapsackIndividuals().length);
        int secondIndex = random.nextInt(knapsackPopulation.getKnapsackIndividuals().length);
        return (knapsackPopulation.getKnapsackIndividuals()[firstIndex].getFitnessValue() >= knapsackPopulation.getKnapsackIndividuals()[secondIndex].getFitnessValue()) ?
                knapsackPopulation.getKnapsackIndividuals()[firstIndex] : knapsackPopulation.getKnapsackIndividuals()[secondIndex];
    }

    public static KnapsackIndividual performLinearRankSelection(KnapsackPopulation knapsackPopulation, GAConfig gaConfig) {
        double randX = Math.random();
        double cParam = gaConfig.getSelectPressureParam();
        double cSq = Math.pow(cParam, 2.0);
        double difference1 = cSq - 4.0*(cParam - 1.0)*randX;
        double diff1Sqrt = Math.sqrt(difference1);
        double difference2 = cParam - diff1Sqrt;
        double numberOfIndidivuals = knapsackPopulation.getKnapsackIndividuals().length;
        double ratio = (numberOfIndidivuals * difference2) / ( 2.0 * cParam - 2.0);
        int index = (int)Math.floor(ratio);

        return knapsackPopulation.getKnapsackIndividuals()[index];
    }

    public static int performCrossover(KnapsackIndividual firstParent, KnapsackIndividual secondParent, KnapsackPopulation newPopulation, int numberOfNewIndividuals) {

        KnapsackIndividual copyOfFirst = new KnapsackIndividual(firstParent);
        KnapsackIndividual copyOfSecond = new KnapsackIndividual(firstParent);

        Random random = new Random(System.currentTimeMillis());
        int middleIndex = random.nextInt(firstParent.getKnapsackItems().length + 1);

        for (int i = 0; i < middleIndex; i++) {
            boolean tmpBoolean = copyOfFirst.getKnapsackItems()[i].isUsed();
            copyOfFirst.getKnapsackItems()[i].setUsed(copyOfSecond.getKnapsackItems()[i].isUsed());
            copyOfSecond.getKnapsackItems()[i].setUsed(tmpBoolean);
        }

        newPopulation.getKnapsackIndividuals()[numberOfNewIndividuals++] = copyOfFirst;
        newPopulation.getKnapsackIndividuals()[numberOfNewIndividuals++] = copyOfSecond;
        return numberOfNewIndividuals;
    }

    private static double getValueSumOfUsedItems(KnapsackIndividual knapsackIndividual) {
        double valueSum = 0.0;
        KnapsackItem[] knapsackItems = knapsackIndividual.getKnapsackItems();
        for(KnapsackItem knapsackItem : knapsackItems) {
            if(knapsackItem.isUsed()) {
                valueSum += knapsackItem.getValue();
            }
        }
        return valueSum;
    }

    private static int getWeightOfUsedItems(KnapsackIndividual knapsackIndividual) {
        int weightSum = 0;
        KnapsackItem[] knapsackItems = knapsackIndividual.getKnapsackItems();
        for(KnapsackItem knapsackItem : knapsackItems) {
            if(knapsackItem.isUsed()) {
                weightSum += knapsackItem.getWeight();
            }
        }
        return weightSum;
    }


    public static void performMutation(KnapsackPopulation newPopulation, GAConfig gaConfig) {
        double probOfMutation = gaConfig.getProbOfMutation();

        KnapsackIndividual[] knapsackIndividuals = newPopulation.getKnapsackIndividuals();
        for(KnapsackIndividual knapsackIndividual : knapsackIndividuals) {
            if(knapsackIndividual != null) {
                KnapsackItem[] knapsackItems = knapsackIndividual.getKnapsackItems();
                for (KnapsackItem knapsackItem : knapsackItems) {
                    if (Math.random() < probOfMutation) {
                        knapsackItem.setUsed(!knapsackItem.isUsed());
                    }
                }
            }
        }
    }
}
